package edu.fsu.cs.buzbee.steggo;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class MatrixDecoder {
	BufferedImage image;
	
	public MatrixDecoder(BufferedImage secret)
	{
		if (secret == null)
			throw new NullPointerException("Image cannot be null");
		if (secret.getWidth() * secret.getHeight() == 0)
			throw new NullPointerException("Image contains 0 pixels");
		
		image = secret;
	}
	
	Point linearToPoint(long offset)
	{
		long y = offset / image.getWidth();
		long x = offset - (image.getWidth()*y);
		
		return new Point((int)x, (int)y);
	}
	
	/**
	 * Recovers data from the image
	 * @param target Location to save extracted data
	 * @param k The size k used to encode
	 * @param size The size of the file being decoded - necessary due to padding
	 * @throws IOException Thrown when there was an error writing to a data file
	 * @throws InvalidFormatException Thrown when the image is not in a valid format
	 */
	public void decode(File target, long k, long size) throws IOException, InvalidFormatException
	{	
		System.out.println("Decoding, k="+k);
		long componentBytes = (image.getWidth() * image.getHeight()*3);
		long n = (1L << k) - 1L;
		long numN = (int)Math.floor(componentBytes / n);
		FileOutputStream fout = new FileOutputStream(target);
		long count = 0;
		for (int i = 0; i < numN; ++i)
		{
			long MB = b(i, n);
			for (long k_this = k;  k_this > 0; k_this -= 8)
			{
				
				fout.write((int)((MB >> k_this - 8) & 0x000000FF));
				++count;
				if (count >= size) 
				{
					fout.close();
					return;
				}
			}
		}
		fout.close();
		
	}
	/**
	 * Gets least significant bit at location
	 * Example: Get the LSB of the 2nd (green) component of the first pixel with location = 1
	 *          Get the LSB of the 3rd component of the 5th pixel with location = 17
	 * @param location
	 * @return
	 */
	public int getLSB(long location)
	{
		if (location < 0 || location > (image.getWidth() * image.getHeight() * 3))
				throw new IndexOutOfBoundsException("Location out of bounds: "+location);
		
		long pixel = location / 3;
		int mask;
		if (location % 3 == 0)
			mask = 0x00010000;
		else if (location % 3 == 1)
			mask = 0x00000100;
		else
			mask = 0x00000001;
		
		Point p = linearToPoint(pixel);
		int rgb = image.getRGB(p.x, p.y);
		if ((rgb & mask) != 0) return 1;
		else return 0;
	}

	/**
	 * Calculates the b(cb) function for the cover block of size N starting at bit N*cb_offset
	 * First cb, cb_offset = 0
	 * Second cb, cb_offset = 1
	 * ...
	 * @param cb_offset
	 * @return
	 */
	public long b(int cb_offset, long n)
	{
		long result = 0;
		for (long position = cb_offset*n, j = 1; j <= n; j++, position++)
		{
			result ^= getLSB(position) * j;
		}
		return result;
	}
}
